/*******************************************************************************
 * Venice Noise Android Application
 * Copyright (C) 2011  Worcester Polytechnic Institute, Wesley Ripley
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * If you plan to use or modify any of this code please contact Fabio Carrera (carrera.fabio@gmail.com)
 * Also please attribute any code used in the final product to the developers. 
 * 
 * Author: Wesley Ripley (wripley@wpi.edu) 
 *******************************************************************************/
package org.venicenoise.app;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.util.Log;

/**
 * A class for recording raw audio stored as WAV files.
 * 
 * Adapted from algorithm in 
 * Every, S. (2010). Pro Android Media: Developing Graphics, Music, Video, and Rich Media Apps for Smartphones and Tablets 
 * (1-4302-3267-6, 978-1-4302-3267-4). 
 * 
 * It is worth noting that if you simply want an audio file and do not care about the compression or "rawness" of
 * the recording there are easier and more reliable ways to record audio in android. See the source cited above
 * for more options
 * 
 * @author Wesley Ripley
 * @version 12/15/11
 *
 */
public class RecordAudio extends AsyncTask<Void, Integer, Void> {
	/**
	 * The default frequency (or sample rate) to record at
	 */
    public final static int DEFAULT_FREQUENCY = 44100;
    /**
     * The default channel configuration
     */
    public final static int DEFAULT_CHANNEL = AudioFormat.CHANNEL_IN_MONO;
    /**
     * The default audio file encoding
     */
    public final static int DEFAULT_ENCODING = AudioFormat.ENCODING_PCM_16BIT;
    
	private final File recordingFile;

	private final int frequency;
	private final int channelConfiguration;
	private final int audioEncoding;

	private boolean isRecording = false;
	private int dataSize;
	
	private DataOutputStream dos;
	private RandomAccessFile randomAccessWriter;
	private AudioRecord audioRecord;

	/**
	 * Constructor for RecordAudio. Simply record to a file using the defaults
	 * @param file
	 * 		The file to record to
	 */
	public RecordAudio(File file) {
		this(file,DEFAULT_FREQUENCY,DEFAULT_CHANNEL,DEFAULT_ENCODING);
	}

	/**
	 * Constructor for RecordAudio. 
	 * @param file
	 * 		The file to record to
	 * @param frequency
	 * 		The frequency (or sample rate) at which to record
	 * @param channelConfiguration
	 * 		The channel configuration to use when recording.
	 * 		Use one of the constants from AudioFormat.
	 * @param audioEncoding
	 * 		The audio encoding to use when recording.
	 * 		Use one of the constants from AudioFormat.
	 */
	public RecordAudio(File file, int frequency, int channelConfiguration,
			int audioEncoding) {
		recordingFile = file;
		this.frequency = frequency;
		this.channelConfiguration = channelConfiguration;
		this.audioEncoding = audioEncoding;
		
	}

	/** Record audio in the background*/
	@Override
	protected Void doInBackground(Void... params) {
		isRecording = true;

		try {
			randomAccessWriter = new RandomAccessFile(recordingFile, "rw");
			writeHeader();
			
			int bufferSize = AudioRecord.getMinBufferSize(frequency,
															channelConfiguration, audioEncoding);

			AudioRecord audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, frequency,
														channelConfiguration, audioEncoding, bufferSize);

			short[] buffer = new short[bufferSize];
			audioRecord.startRecording();

			while (isRecording) {
				//read directly from microphone each sample, and write directly into the file
				int bufferReadResult = audioRecord.read(buffer, 0, bufferSize);
				for (int i = 0; i < bufferReadResult; i++) {
					randomAccessWriter.writeShort(Short.reverseBytes(buffer[i]));
				}
				//keeo track of the size of the data we are writing for use in the header
				dataSize += bufferReadResult*2;
			}
			if(dataSize % 2 != 0)
			{
				randomAccessWriter.writeBoolean(true);//write pad byte
			}
			
			randomAccessWriter.seek(4); // Write size to RIFF header
			randomAccessWriter.writeInt(Integer.reverseBytes(36+dataSize));
		
			randomAccessWriter.seek(40); // Write size to Subchunk2Size field
			randomAccessWriter.writeInt(Integer.reverseBytes(dataSize));

			audioRecord.stop();
			randomAccessWriter.close();
			isRecording = false;
		} catch (Throwable t) {
			if(AppUtil.LOGGING)
			{
				Log.e(AppUtil.LOG_TAG, "Recording Failed");
			}
			isRecording = false;
		}

		return null;
	}
	
	/**
	 * Write the wave header into the file
	 * @throws IOException
	 * 		if there is a problem writing to the file
	 */
	private void writeHeader() throws IOException
	{
		//calculate the number of channels and the sample size
		short numChannels = (short) ((this.channelConfiguration == AudioFormat.CHANNEL_IN_STEREO) ? 2 : 1);
		short sampleSize = (short) ((this.audioEncoding == AudioFormat.ENCODING_PCM_16BIT) ? 16 : 8);
		//code adapted from http://i-liger.com/article/android-wav-audio-recording
		randomAccessWriter.setLength(0); // Set file length to 0, to prevent unexpected behavior in case the file already existed
		randomAccessWriter.writeBytes("RIFF");
		randomAccessWriter.writeInt(0); // Final file size not known yet, write 0 
		randomAccessWriter.writeBytes("WAVE");
		randomAccessWriter.writeBytes("fmt ");
		randomAccessWriter.writeInt(Integer.reverseBytes(16)); // Sub-chunk size, 16 for PCM
		randomAccessWriter.writeShort(Short.reverseBytes((short) 1)); // AudioFormat, 1 for PCM
		randomAccessWriter.writeShort(Short.reverseBytes(numChannels));// Number of channels, 1 for mono, 2 for stereo
		randomAccessWriter.writeInt(Integer.reverseBytes(frequency)); // Sample rate
		randomAccessWriter.writeInt(Integer.reverseBytes(frequency*numChannels*sampleSize/8)); // Byte rate, SampleRate*NumberOfChannels*BitsPerSample/8
		randomAccessWriter.writeShort(Short.reverseBytes((short)(numChannels*sampleSize/8))); // Block align, NumberOfChannels*BitsPerSample/8
		randomAccessWriter.writeShort(Short.reverseBytes(sampleSize)); // Bits per sample
		randomAccessWriter.writeBytes("data");
		randomAccessWriter.writeInt(0); // Data chunk size not known yet, write 0
	}
	
	/** Called when the background task gets cancelled*/
	@Override
	public void onCancelled()
	{
		isRecording = false;
		audioRecord.stop();
		try {
			dos.close();
		} catch (IOException e) {
			//Purposely do nothing
		}
	}
	/**
	 * Check if currently recording
	 * @return
	 * 		true if actively recording, false otherwise
	 */
	public boolean isRecording() {
		return isRecording;
	}
	
	/**
	 * Start recording audio in the background
	 */
	public void startRecoding()
	{
		dataSize = 0;
		execute();
	}
	
	/**
	 * Stop the recording
	 */
	public void stopRecording()
	{
		isRecording = false;
		
	}
}